import json
import random
import time
import traceback
from logging.handlers import RotatingFileHandler

from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QDateTime, QObject, pyqtSignal, QRunnable, QThreadPool, Qt
from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QSpinBox, QComboBox, QMessageBox, QMenu, QAction, QInputDialog, \
    QFileDialog, QDoubleSpinBox, QFormLayout
import sys
import os
from PyQt5.QtWidgets import QApplication
from DrissionPage import ChromiumPage
import urllib3
import logging
from frames.widgets.CommandWidget import CommandWidget

try:
    from .config import FLOW_ITEM_S
except Exception as e:
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    from config import FLOW_ITEM_S

try:
    from .frames.DataCollectWidget import Ui_FlowDataCollectWidget
except Exception as e:
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    from frames.DataCollectWidget import Ui_FlowDataCollectWidget
try:
    from .myvmp.FlowBus import FlowBus
except Exception as e:
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    from myvmp.FlowBus import FlowBus

# 忽略警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

__all__ = [
    'logger'
]

# logger 声明
logger = logging.getLogger(__name__)

__LOG_DIR_NAME = 'logs'  # 日志输出目录


def __init_logger():
    global logger
    logger.setLevel(logging.DEBUG)
    # 设置保存路径为 项目路径
    # logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), __LOG_DIR_NAME)
    logs_dir = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), __LOG_DIR_NAME)
    if not os.path.exists(logs_dir):
        os.makedirs(logs_dir)
    program = os.path.basename(sys.argv[0]).split(".")[0]
    now_str = time.strftime("%Y-%m-%d", time.localtime())
    log_file = os.path.join(logs_dir, "{}_{}.log".format(program, now_str))
    rotating_file_handler = RotatingFileHandler(log_file, maxBytes=10 * 1024 * 1024, backupCount=1)
    rotating_file_handler.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)
    # 设置日志格式
    # formatter = logging.Formatter(
    #     "%(asctime)s | %(levelname)s | %(filename)s : %(funcName)s : %(lineno)s - %(message)s")
    formatter = logging.Formatter(
        "%(asctime)s | %(levelname)s |  : %(lineno)s - %(message)s")
    rotating_file_handler.setFormatter(formatter)
    logger.addHandler(rotating_file_handler)
    logger.addHandler(ch)


__init_logger()


def output_log(log_info, log_level='info'):
    """
    输出日志
    :param log_info: 日志信息
    :param log_level: 日志级别
    :return:
    """
    if log_level == 'info':
        logger.info(log_info)
    elif log_level == 'error':
        logger.error(log_info)
    elif log_level == 'warning':
        logger.warning(log_info)
    elif log_level == 'debug':
        logger.debug(log_info)


class FlowWorkerSignals(QObject):
    """
    上传线程信号
    """
    message_signal = pyqtSignal(str, str)
    stop_signal = pyqtSignal()
    status_signal = pyqtSignal(int, bool)


class FlowWorker(QRunnable):
    """
    流程执行线程
    """

    def __init__(self, parent, signals, flow_list):
        super().__init__()
        self.parent = parent
        self.signals = signals
        self.flow_list = flow_list
        self.is_running = True
        self.chrome_page = None

    def run(self):
        """
        运行
        :return:
        """
        self.signals.message_signal.emit('开始执行流程', 'info')
        self.chrome_page = ChromiumPage()
        flow_bus = FlowBus(self.flow_list,
                           self.signals.message_signal,
                           self.signals.status_signal,
                           self.chrome_page,
                           self)
        try:
            flow_bus.run()
        except Exception as e:
            traceback.print_exc()
            self.signals.message_signal.emit(f'执行流程出现异常:{e}', 'error')
        finally:
            self.signals.stop_signal.emit()

    def stop(self):
        self.is_running = False


class DataCollectWidgetFunctionStarter(Ui_FlowDataCollectWidget):
    def __init__(self, parent=None):
        super(DataCollectWidgetFunctionStarter, self).setupUi(parent)
        self.parent = parent
        self.flow_item = FLOW_ITEM_S
        # 流程执行信号量
        self.signals = FlowWorkerSignals()
        # 线程池
        self.thread_pool = QThreadPool.globalInstance()
        # 默认最大同时运行线程数为1
        self.thread_pool.setMaxThreadCount(5)
        # 窗体数据
        self.frame_data = None
        # 窗体数据保存的路径
        self.frame_data_save_path = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'flow_config.json')
        self.init_ui()
        self.chrome_page = None
        self.job_worker = None

    def init_ui(self):
        """
        初始化界面
        :return:
        """
        self.init_flow_combo_box()
        self.init_event()
        self.init_context_menu()
        # 把暂停按钮设置为不可用
        self.stop_btn.setEnabled(False)
        # 设置flow_items_table_widget的第一列宽度
        self.flow_items_table_widget.setColumnWidth(0, 50)
        self.read_config()
        self.init_flow_group_list_widget_data()
        # 如果流程组有数据，默认选中第一个
        if self.flow_group_list_widget.count() > 0:
            self.flow_group_list_widget.setCurrentRow(0)
        self.out_to_text_browser('窗体初始化成功.......')

    def init_event(self):
        """
        初始化事件
        :return:
        """
        # 绑定改变事件
        # self.flow_combo_box.currentIndexChanged.connect(self.flow_combo_box_changed)
        # 绑定启动chrome页面事件
        self.start_chrome_page_btn.clicked.connect(self.start_chrome_page)
        # # 插到结尾
        # self.insert_end_btn.clicked.connect(self.insert_end)
        # # 插到开头
        # self.insert_start_btn.clicked.connect(self.insert_start)
        # # 插到前面
        # self.insert_before_btn.clicked.connect(self.insert_before)
        # # 插到后面
        # self.insert_after_btn.clicked.connect(self.insert_after)
        # 开始
        self.start_btn.clicked.connect(self.start_flow)
        # 停止
        self.stop_btn.clicked.connect(self.stop_flow)
        # 左侧流程组列表选中改变事件
        self.flow_group_list_widget.itemSelectionChanged.connect(self.flow_group_list_widget_selected_changed)
        # flow_items_table_widget选中行改变事件
        self.flow_items_table_widget.itemSelectionChanged.connect(self.flow_item_table_widget_selected_changed)
        # flow_items_table_widget双击事件,运行当前行或者运行循环事件
        self.flow_items_table_widget.itemDoubleClicked.connect(self.single_flow_run)
        # 绑定输出到运行日志框的信号
        self.signals.message_signal.connect(self.out_to_text_browser)
        # 绑定完成运行信号
        self.signals.stop_signal.connect(self.finish_flow)
        # 状态更新信号
        self.signals.status_signal.connect(self.update_status)
        # 设置导入按钮
        self.import_flow_group_btn.clicked.connect(self.import_flow_group)

    def init_context_menu(self):
        """
        初始化右键菜单
        :return:
        """
        # 设置流程组列表右键菜单
        self.flow_group_list_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.flow_group_list_widget.customContextMenuRequested.connect(self.show_flow_group_list_widget_context_menu)
        # 设置流程表右键菜单
        self.flow_items_table_widget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.flow_items_table_widget.customContextMenuRequested.connect(self.flow_table_widget_context_menu)

    def flow_table_widget_context_menu(self, pos):
        """
        流程表右键菜单
        :param pos:  位置
        :return:
        """
        menu = QMenu(self.parent)
        menu.addAction(QAction('删除选中行', self.parent))
        menu.addAction(QAction('上移选中行', self.parent))
        menu.addAction(QAction('下移选中行', self.parent))
        menu.addAction(QAction('移到开头', self.parent))
        menu.addAction(QAction('移到结尾', self.parent))
        # 显示菜单
        action = menu.exec_(self.flow_items_table_widget.mapToGlobal(pos))
        if not action:
            return
        if action.text() == '删除选中行':
            self.delete_flow_item()
        if action.text() == '上移选中行':
            self.move_up_flow_item()
        if action.text() == '下移选中行':
            self.move_down_flow_item()
        if action.text() == '移到开头':
            self.move_to_top_flow_item()
        if action.text() == '移到结尾':
            self.move_to_bottom_flow_item()

    def show_flow_group_list_widget_context_menu(self, pos):
        """
        显示流程组列表右键菜单
        :param pos:  位置
        :return:  None
        """
        menu = QMenu(self.parent)
        menu.addAction(QAction('删除选中组', self.parent))
        menu.addAction(QAction('新增流程组', self.parent))
        menu.addAction(QAction('编辑追踪组', self.parent))
        menu.addAction(QAction('导出选中组', self.parent))
        # 显示菜单
        action = menu.exec_(self.flow_group_list_widget.mapToGlobal(pos))
        if not action:
            return
        if action.text() == '删除选中组':
            self.delete_flow_group()
        if action.text() == '新增流程组':
            self.add_flow_group()
        if action.text() == '编辑追踪组':
            group_name = self.flow_group_list_widget.currentItem().text()
            self.edit_flow_group(group_name)
        if action.text() == '导出选中组':
            self.export_flow_group()

    def delete_flow_item(self):
        """
        删除流程表选中行
        :return:
        """
        rows = self.flow_items_table_widget.selectionModel().selectedRows()
        if len(rows) == 0:
            QMessageBox.warning(self.parent, '提示', '请先选择要删除的行')
            return
        rows = sorted(rows, key=lambda x: x.row(), reverse=True)
        for row in rows:
            self.flow_items_table_widget.removeRow(row.row())
        # 刷新一次序号
        self.refresh_flow_index()
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def move_up_flow_item(self):
        """
        上移流程表选中行
        :return:
        """
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要上移的行')
            return
        if row == 0:
            QMessageBox.warning(self.parent, '提示', '已经是第一行了')
            return
        # 获取当前行颜色
        current_row_color = self.flow_items_table_widget.item(row, 0).background().color()
        # 获取选中行的数据
        flow_data = []
        for i in range(self.flow_items_table_widget.columnCount()):
            flow_data.append(self.flow_items_table_widget.item(row, i).text())
        # 删除选中行
        self.flow_items_table_widget.removeRow(row)
        # 插入到上一行
        self.flow_items_table_widget.insertRow(row - 1)
        for i in range(self.flow_items_table_widget.columnCount()):
            self.flow_items_table_widget.setItem(row - 1, i, QtWidgets.QTableWidgetItem(flow_data[i]))
        # 设置颜色
        self.flow_items_table_widget.item(row - 1, 0).setBackground(QtGui.QColor(current_row_color))
        self.flow_items_table_widget.item(row - 1, 1).setBackground(QtGui.QColor(current_row_color))
        # 刷新一次序号
        self.refresh_flow_index()
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def move_down_flow_item(self):
        """
        下移流程表选中行
        :return:
        """
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要下移的行')
            return
        if row == self.flow_items_table_widget.rowCount() - 1:
            QMessageBox.warning(self.parent, '提示', '已经是最后一行了')
            return
        # 获取当前行颜色
        current_row_color = self.flow_items_table_widget.item(row, 0).background().color()
        # 获取选中行的数据
        flow_data = []
        for i in range(self.flow_items_table_widget.columnCount()):
            flow_data.append(self.flow_items_table_widget.item(row, i).text())
        # 删除选中行
        self.flow_items_table_widget.removeRow(row)
        # 插入到下一行
        self.flow_items_table_widget.insertRow(row + 1)
        for i in range(self.flow_items_table_widget.columnCount()):
            self.flow_items_table_widget.setItem(row + 1, i, QtWidgets.QTableWidgetItem(flow_data[i]))
        # 设置颜色
        self.flow_items_table_widget.item(row + 1, 0).setBackground(QtGui.QColor(current_row_color))
        self.flow_items_table_widget.item(row + 1, 1).setBackground(QtGui.QColor(current_row_color))
        # 刷新一次序号
        self.refresh_flow_index()
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def move_to_top_flow_item(self):
        """
        移动到最上面
        :return:
        """
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要移动的行')
            return
        if row == 0:
            QMessageBox.warning(self.parent, '提示', '已经是第一行了')
            return
        # 获取当前行颜色
        current_row_color = self.flow_items_table_widget.item(row, 0).background().color()
        # 获取选中行的数据
        flow_data = []
        for i in range(self.flow_items_table_widget.columnCount()):
            flow_data.append(self.flow_items_table_widget.item(row, i).text())
        # 删除选中行
        self.flow_items_table_widget.removeRow(row)
        # 插入到第一行
        self.flow_items_table_widget.insertRow(0)
        for i in range(self.flow_items_table_widget.columnCount()):
            self.flow_items_table_widget.setItem(0, i, QtWidgets.QTableWidgetItem(flow_data[i]))
        # 设置颜色
        self.flow_items_table_widget.item(0, 0).setBackground(QtGui.QColor(current_row_color))
        self.flow_items_table_widget.item(0, 1).setBackground(QtGui.QColor(current_row_color))
        # 刷新一次序号
        self.refresh_flow_index()
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def move_to_bottom_flow_item(self):
        """
        移动到最下面
        :return:
        """
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要移动的行')
            return
        if row == self.flow_items_table_widget.rowCount() - 1:
            QMessageBox.warning(self.parent, '提示', '已经是最后一行了')
            return
        # 获取当前行颜色
        current_row_color = self.flow_items_table_widget.item(row, 0).background().color()
        # 获取选中行的数据
        flow_data = []
        for i in range(self.flow_items_table_widget.columnCount()):
            flow_data.append(self.flow_items_table_widget.item(row, i).text())
        # 删除选中行
        self.flow_items_table_widget.removeRow(row)
        # 插入到最后一行
        self.flow_items_table_widget.insertRow(self.flow_items_table_widget.rowCount())
        for i in range(self.flow_items_table_widget.columnCount()):
            self.flow_items_table_widget.setItem(self.flow_items_table_widget.rowCount() - 1, i,
                                                 QtWidgets.QTableWidgetItem(flow_data[i]))
        # 设置颜色
        self.flow_items_table_widget.item(self.flow_items_table_widget.rowCount() - 1, 0).setBackground(
            QtGui.QColor(current_row_color))
        self.flow_items_table_widget.item(self.flow_items_table_widget.rowCount() - 1, 1).setBackground(
            QtGui.QColor(current_row_color))
        # 刷新一次序号
        self.refresh_flow_index()
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def flow_item_table_widget_selected_changed(self):
        """
        流程明细表选中行改变事件
        :return:
        """
        item = self.flow_items_table_widget.currentItem()
        if item is None:
            return
        # 把flow_combo_box置空
        # self.flow_combo_box.setCurrentIndex(-1)
        row = item.row()
        flow_name = self.flow_items_table_widget.item(row, 1).text()
        if flow_name.endswith('尾'):
            # 把params_form_layout中的所有组件清空
            self.clear_params_form_layout()
            # self.out_to_text_browser('无法编辑【尾】流程')
            return
        start_delay = int(self.flow_items_table_widget.item(row, 2).text())
        end_delay = int(self.flow_items_table_widget.item(row, 3).text())
        params = json.loads(self.flow_items_table_widget.item(row, 4).text())
        if flow_name.endswith('首'):
            flow_name = flow_name[:-1]
        # 原始参数结构,copy一份self.flow_item[flow_name]
        origin_params = json.loads(json.dumps(self.flow_item[flow_name]))
        for key, value in params.items():
            if key in origin_params.keys():
                origin_params[key]['now_value'] = value
        self.init_flow_params(origin_params)
        self.add_update_btn()
        self.flow_start_delay_spin_box.setValue(start_delay)
        self.flow_end_delay_spin_box.setValue(end_delay)

    def find_start_or_end_flow(self, current_row_num):
        """
        通过当前行号找到对应的首行号或者尾行号
        :param current_row_num: 当前行号
        :return: 具体行号
        """
        # 取当前行流程的内容
        current_row_flow_name_item = self.flow_items_table_widget.item(current_row_num, 1)
        current_row_color = current_row_flow_name_item.background().color().name()
        current_row_flow_name = current_row_flow_name_item.text()
        start_for_index = current_row_num + 1
        end_for_index = self.flow_items_table_widget.rowCount()
        step = 1
        if current_row_flow_name.endswith('尾'):
            start_for_index = current_row_num - 1
            end_for_index = -1
            step = -1
        for index in range(start_for_index, end_for_index, step):
            index_row_name_item = self.flow_items_table_widget.item(index, 1)
            color_name = index_row_name_item.background().color().name()
            if current_row_color == color_name:
                return index
        return -1

    def single_flow_run(self):
        """
        单步运行
        :return:
        """
        # 获取当前选中行
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要运行的行')
            return
        self.chrome_page = ChromiumPage()
        # 所有的状态改成待执行
        self.init_all_flow_status()
        all_flow = self.get_flow()
        flow = all_flow[row]
        # 取当前行内容
        current_row_flow_name_item = self.flow_items_table_widget.item(row, 1)
        if current_row_flow_name_item.text().endswith('尾') or current_row_flow_name_item.text().endswith('首'):
            index = self.find_start_or_end_flow(row)
            if index == -1:
                QMessageBox.warning(self.parent, '提示', '未找到对应的首或尾')
                return
            # 判断index和row的大小,如果index大于row,则从row到index,否则从index到row
            start_index, end_index = min(index, row), max(index, row)
            flow = all_flow[start_index:end_index + 1]
            self.execute_flow(flow)
        else:
            self.execute_flow([flow])

    def edit_flow_group(self, group_name):
        """
        编辑流程组
        :return:
        """
        # 弹出一个输入框
        text, ok = QInputDialog.getText(self.parent, '编辑流程组', '请输入流程组名称:', QLineEdit.Normal,
                                        group_name)
        if ok:
            if text == '':
                QMessageBox.warning(self.parent, '提示', '流程组名称不能为空')
                return
            if text != group_name and text in self.frame_data.keys():
                QMessageBox.warning(self.parent, '提示', '流程组名称已存在')
                return
            else:
                self.flow_group_list_widget.currentItem().setText(text)
                self.frame_data[text] = self.frame_data.pop(group_name)
                self.save_config()

    def delete_flow_group(self):
        """
        删除流程组
        :return:
        """
        group_name = self.flow_group_list_widget.currentItem().text()
        if group_name == '':
            QMessageBox.warning(self.parent, '提示', '请选择要删除的流程组')
            return
        reply = QMessageBox.question(self.parent, '提示', f'确定要删除流程组【{group_name}】吗?',
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.flow_group_list_widget.takeItem(self.flow_group_list_widget.currentRow())
            self.frame_data.pop(group_name)
            self.save_config()

    def add_flow_group(self):
        """
        添加流程组
        :return:
        """
        # 弹出一个输入框
        text, ok = QInputDialog.getText(self.parent, '添加流程组', '请输入流程组名称:')
        if ok:
            # 设置输入的文本
            self.flow_group_list_widget.addItem(text)
            self.frame_data[text] = []
            self.save_config()

    def export_flow_group(self):
        """
        导出流程组
        :return:
        """
        group_name = self.flow_group_list_widget.currentItem().text()
        if group_name == '':
            QMessageBox.warning(self.parent, '提示', '请选择要导出的流程组')
            return
        file_path, _ = QFileDialog.getSaveFileName(self.parent, '导出流程组', '', 'json文件(*.json)')
        if file_path:
            with open(file_path, 'w', encoding='utf-8') as f:
                export_data = {
                    "groupName": group_name,
                    "flowData": self.frame_data[group_name]
                }
                json.dump(export_data, f, ensure_ascii=False, indent=4)

    def import_flow_group(self):
        """
        导入流程组
        :return:
        """
        file_path, _ = QFileDialog.getOpenFileName(self.parent, '导入流程组', '', 'json文件(*.json)')
        if file_path:
            with open(file_path, 'r', encoding='utf-8') as f:
                import_data = json.load(f)
                group_name = import_data['groupName']
                insert_group_name = group_name
                flow_data = import_data['flowData']
                index = 1
                if insert_group_name in self.frame_data.keys():
                    while True:
                        insert_group_name = f'{group_name}_{index}'
                        if insert_group_name in self.frame_data.keys():
                            index += 1
                            continue
                        QMessageBox.warning(self.parent, '提示', f'流程组已存在,已重命名为【{insert_group_name}】')
                        break
                self.flow_group_list_widget.addItem(insert_group_name)
                self.frame_data[insert_group_name] = flow_data
                self.save_config()

    def read_config(self):
        """
        读取配置
        :return:
        """
        if os.path.exists(self.frame_data_save_path):
            with open(self.frame_data_save_path, 'r', encoding='utf-8') as f:
                self.frame_data = json.load(f)
        else:
            self.frame_data = {}

    def save_config(self):
        """
        保存配置
        :return:
        """
        with open(self.frame_data_save_path, 'w', encoding='utf-8') as f:
            json.dump(self.frame_data, f, ensure_ascii=False, indent=4)

    def flow_group_list_widget_selected_changed(self):
        """
        流程组列表选中项改变事件
        :return:
        """
        self.flow_items_table_widget.clearContents()
        self.flow_items_table_widget.setRowCount(0)
        group_name = self.flow_group_list_widget.currentItem().text()
        if group_name in self.frame_data.keys():
            for flow in self.frame_data[group_name]:
                row = self.flow_items_table_widget.rowCount()
                self.flow_items_table_widget.insertRow(row)
                self.flow_items_table_widget.setItem(row, 0,
                                                     QtWidgets.QTableWidgetItem(
                                                         str(self.flow_items_table_widget.rowCount())))
                self.flow_items_table_widget.setItem(row, 1, QtWidgets.QTableWidgetItem(flow['flow_name']))
                self.flow_items_table_widget.setItem(row, 2,
                                                     QtWidgets.QTableWidgetItem(str(flow['flow_start_delay'])))
                self.flow_items_table_widget.setItem(row, 3,
                                                     QtWidgets.QTableWidgetItem(str(flow['flow_end_delay'])))
                self.flow_items_table_widget.setItem(row, 4, QtWidgets.QTableWidgetItem(
                    json.dumps(flow['params'], ensure_ascii=False)))
                self.flow_items_table_widget.setItem(row, 5, QtWidgets.QTableWidgetItem('待执行'))
                # 设置颜色,如果有颜色的话,且颜色不是黑色或者白色
                color = flow.get('color')
                if color and color != '#000000' and color != '#ffffff':
                    # 设置颜色
                    self.flow_items_table_widget.item(row, 0).setBackground(QtGui.QColor(color))
                    self.flow_items_table_widget.item(row, 1).setBackground(QtGui.QColor(color))

    def init_flow_group_list_widget_data(self):
        """
        初始化流程组数据
        :return:
        """
        for key in self.frame_data.keys():
            self.flow_group_list_widget.addItem(key)

    def init_flow_combo_box(self):
        """
        初始化指令流程框
        :return:
        """
        cmd_widget = CommandWidget(FLOW_ITEM_S, self)
        self.cmd_verticalLayout.addWidget(cmd_widget)
        # 设置内边距为 0
        self.cmd_verticalLayout.setContentsMargins(0, 0, 0, 0)

    def init_flow_params(self, params):
        """
        初始化流程参数,根据参数初始化界面
        :param params:  参数
        :return:  None
        """
        # 把params_form_layout中的所有组件清空
        self.clear_params_form_layout()
        if not params:
            return
        for key, value in params.items():
            if key == 'need_start_end' or key == 'type':
                continue
            self.add_param_widget(key, value)
        self.flow_start_delay_spin_box.setValue(0)
        self.flow_end_delay_spin_box.setValue(0)

    def clear_params_form_layout(self):
        """
        清空参数表单
        :return:
        """
        while self.params_form_layout.count():
            item = self.params_form_layout.takeAt(0)
            widget = item.widget()
            if widget:
                widget.deleteLater()

    def add_param_widget(self, key, value: dict):
        """
        添加参数组件,根据参数类型添加不同的组件
        :param key:  参数名
        :param value:  参数值,其中包含了参数的输入类型
        :return:
        """
        if value['type'] == 'input':
            self.add_input_widget(key, value)
        if value['type'] == 'spinbox':
            self.add_spinbox_widget(key, value)
        if value['type'] == 'combobox':
            self.add_combobox_widget(key, value)
        if value['type'] == 'doubleSpinbox':
            self.add_double_spinbox_widget(key, value)

    def add_double_spinbox_widget(self, key, value):
        """
        添加一个double_spinbox
        :param key: 参数名
        :param value: 参数值
        :return:
        """
        label = QLabel(value['desc'])
        double_spinbox = QDoubleSpinBox()
        double_spinbox.setObjectName(key + '_doubleSpinbox')
        # 设置默认值
        if 'default' in value:
            double_spinbox.setValue(value['default'])
        # 设置最小宽度
        double_spinbox.setMinimumWidth(100)
        if 'now_value' in value:
            double_spinbox.setValue(value['now_value'])
        self.params_form_layout.addRow(label, double_spinbox)

    def add_input_widget(self, key, value):
        """
        添加一个输入框
        :param key: 参数名
        :param value: 参数值
        :return:
        """
        label = QLabel(value['desc'])
        input_ = QLineEdit()
        input_.setPlaceholderText('请输入' + key)
        input_.setObjectName(key + '_input')
        if 'now_value' in value:
            input_.setText(value['now_value'])
        self.params_form_layout.addRow(label, input_)

    def add_spinbox_widget(self, key, value):
        """
        添加一个spinbox
        :param key: 参数名
        :param value: 参数值
        :return:
        """
        label = QLabel(value['desc'])
        spinbox = QSpinBox()
        spinbox.setObjectName(key + '_spinbox')
        # 设置默认值
        if 'default' in value:
            spinbox.setValue(value['default'])
        # 设置最小宽度
        spinbox.setMinimumWidth(100)
        if 'now_value' in value:
            spinbox.setValue(value['now_value'])
        self.params_form_layout.addRow(label, spinbox)

    def add_combobox_widget(self, key, value):
        """
        添加一个下拉框
        :param key:  参数名
        :param value:  参数值
        :return: None
        """
        label = QLabel(value['desc'])
        combobox = QComboBox()
        combobox.setObjectName(key + '_combobox')
        combobox.addItems(value['options'])
        if 'now_value' in value:
            combobox.setCurrentText(value['now_value'])
        self.params_form_layout.addRow(label, combobox)

    def add_update_btn(self):
        """
        添加更新按钮
        :return:
        """
        btn = QtWidgets.QPushButton('更新')
        btn.clicked.connect(self.update_flow_item)
        self.params_form_layout.addRow(btn)

    def update_flow_item(self):
        """
        更新流程表选中行
        :return:
        """
        row = self.flow_items_table_widget.currentRow()
        if row == -1:
            QMessageBox.warning(self.parent, '提示', '请先选择要更新的行')
            return
        flow_name = self.flow_items_table_widget.item(row, 1).text()
        flow_name = flow_name[:-1] if flow_name.endswith('首') or flow_name.endswith('尾') else flow_name
        params = self.get_params_form(flow_name=flow_name)
        if params is None:
            return
        start_delay = self.flow_start_delay_spin_box.value()
        end_delay = self.flow_end_delay_spin_box.value()
        self.flow_items_table_widget.setItem(row, 2, QtWidgets.QTableWidgetItem(str(start_delay)))
        self.flow_items_table_widget.setItem(row, 3, QtWidgets.QTableWidgetItem(str(end_delay)))
        self.flow_items_table_widget.setItem(row, 4,
                                             QtWidgets.QTableWidgetItem(json.dumps(params, ensure_ascii=False)))
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()
        QMessageBox.information(self.parent, '提示', '更新成功')

    def start_chrome_page(self):
        """
        启动chrome页面
        :return:
        """
        self.chrome_page = ChromiumPage()

    def insert_flow(self, flow_name, user_fill_params):
        """
        插入流程
        :param flow_name:  流程名
        :param user_fill_params:  用户填写的参数
        :return:
        """
        # 取flow_items_table_widget的选中行号
        row_num = self.flow_items_table_widget.currentRow()
        if row_num == -1:
            row_num = self.flow_items_table_widget.rowCount()
        else:
            row_num += 1
        if user_fill_params is None:
            return
        origin_params = json.loads(json.dumps(self.flow_item[flow_name]))
        need_start_end = origin_params.get('need_start_end', False)
        # 解析参数
        if need_start_end:
            self.insert_start_and_end_flow(row_num, user_fill_params, flow_name)
        else:
            self.insert_row(row_num, user_fill_params, flow_name)
        # 刷新流程索引
        self.refresh_flow_index()

    def get_params_form(self, form_layout: QFormLayout = None, flow_name=None):
        """
        获取参数表单
        :param form_layout:  表单布局
        :return:
        """
        params = {}
        # 支持的组件类型
        supported_widget_types = [QLineEdit, QSpinBox, QComboBox, QDoubleSpinBox]
        # 组件类型对应的方法
        widget_type_methods = {
            QLineEdit: ('text', lambda x: x == '' or x is None, '请填写'),
            QSpinBox: ('value', lambda x: x is None, '请填写'),
            QComboBox: ('currentText', lambda x: x == '' or x is None, '请选择'),
            QDoubleSpinBox: ('value', lambda x: x is None, '请填写')
        }
        if form_layout is None:
            form_layout = self.params_form_layout
        for i in range(form_layout.count()):
            widget = form_layout.itemAt(i).widget()
            if widget is None or (type(widget) not in supported_widget_types):
                continue
            key_s = widget.objectName().split('_')
            # 取 0-倒数第二个
            key = '_'.join(key_s[:-1])
            for widget_type, (method_name, condition, message) in widget_type_methods.items():
                if isinstance(widget, widget_type):
                    if hasattr(widget, method_name):
                        content = getattr(widget, method_name)()
                        if condition(content):
                            QMessageBox.warning(self.parent, '提示',
                                                f'{message}{self.flow_item[flow_name][key]["desc"]}')
                            return None
                        params[key] = content
        return params

    def insert_row(self, row, params, flow_name):
        """
        插入一行
        :param row:  行号
        :param params:  参数
        :param flow_name:  流程名
        :return:
        """
        self.flow_items_table_widget.insertRow(row)
        self.flow_items_table_widget.setItem(row, 0,
                                             QtWidgets.QTableWidgetItem(
                                                 str(self.flow_items_table_widget.rowCount())))
        self.flow_items_table_widget.setItem(row, 1, QtWidgets.QTableWidgetItem(flow_name))
        self.flow_items_table_widget.setItem(row, 2,
                                             QtWidgets.QTableWidgetItem(
                                                 str(self.flow_start_delay_spin_box.value())))
        self.flow_items_table_widget.setItem(row, 3,
                                             QtWidgets.QTableWidgetItem(str(self.flow_end_delay_spin_box.value())))
        self.flow_items_table_widget.setItem(row, 4,
                                             QtWidgets.QTableWidgetItem(json.dumps(params, ensure_ascii=False)))
        self.flow_items_table_widget.setItem(row, 5, QtWidgets.QTableWidgetItem('待执行'))
        # 刷新一次配置数据
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def insert_start_and_end_flow(self, row_num, params, flow_name):
        """
        插入开始和结束流程
        :param row_num: 起始行号
        :param params:  参数
        :param flow_name:  流程名
        :return:
        """
        self.insert_row(row_num, params, f'{flow_name}首')
        self.insert_row(row_num + 1, {}, f'{flow_name}尾')
        # # 随机一个颜色，设置颜色，方便区分
        random_color = QtGui.QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        print(random_color.name())
        while True:
            # 遍历flow_items_table_widget，查看是否有重复颜色
            for i in range(self.flow_items_table_widget.rowCount()):
                if self.flow_items_table_widget.item(i, 0).background().color().name() == random_color.name():
                    random_color = QtGui.QColor(random.randint(0, 255), random.randint(0, 255),
                                                random.randint(0, 255))
                    continue
            break
        self.flow_items_table_widget.item(row_num, 0).setBackground(random_color)
        self.flow_items_table_widget.item(row_num, 1).setBackground(random_color)
        self.flow_items_table_widget.item(row_num + 1, 0).setBackground(random_color)
        self.flow_items_table_widget.item(row_num + 1, 1).setBackground(random_color)
        # 再刷新一次配置数据，因为加入了颜色的问题
        self.frame_data[self.flow_group_list_widget.currentItem().text()] = self.get_flow()
        # 保存配置
        self.save_config()

    def refresh_flow_index(self):
        """
        刷新流程索引
        :return:
        """
        for i in range(self.flow_items_table_widget.rowCount()):
            self.flow_items_table_widget.item(i, 0).setText(str(i + 1))

    def start_flow(self):
        """
        开始执行流程
        :return:
        """
        # 初始化所有的状态
        self.init_all_flow_status()
        # 流程合规性检查
        if self.check_flow_compliance():
            # 检查死循环并询问是否继续
            if self.dead_cycle_check():
                reply = QMessageBox.question(self.parent, '提示', '检测到流程中存在死循环,是否继续执行?',
                                             QMessageBox.Yes | QMessageBox.No,
                                             QMessageBox.No)
                if reply == QMessageBox.No:
                    return
            # 获取流程
            flow = self.get_flow()
            # 执行流程
            self.execute_flow(flow)

    def init_all_flow_status(self):
        """
        初始化所有流程状态
        :return:  None
        """
        # 所有值都是待执行
        for i in range(self.flow_items_table_widget.rowCount()):
            self.flow_items_table_widget.setItem(i, 5, QtWidgets.QTableWidgetItem('待执行'))
            # 设置颜色为 4 列的颜色
            self.flow_items_table_widget.item(i, 5).setBackground(
                self.flow_items_table_widget.item(i, 4).background())

    def stop_flow(self):
        """
        停止流程
        :return:
        """
        if self.job_worker is not None:
            self.job_worker.stop()
            self.out_to_text_browser('正在停止流程执行.....')
            self.job_worker = None

    def finish_flow(self):
        """
        完成流程
        :return:
        """
        self.out_to_text_browser('流程执行完成')
        self.job_worker = None
        # 把暂停按钮设置为可用
        self.stop_btn.setEnabled(False)
        # 把开始按钮设置为不可用
        self.start_btn.setEnabled(True)

    def update_status(self, row_num: int, status: bool):
        """
        更新状态
        :param row_num:  行号
        :param status:  状态
        :return:
        """
        if status:
            self.flow_items_table_widget.setItem(row_num, 5, QtWidgets.QTableWidgetItem('已执行'))
            # 设置颜色为绿色
            self.flow_items_table_widget.item(row_num, 5).setBackground(QtGui.QColor('#00ff00'))
        else:
            self.flow_items_table_widget.setItem(row_num, 5, QtWidgets.QTableWidgetItem('执行失败'))
            # 设置颜色为红色
            self.flow_items_table_widget.item(row_num, 5).setBackground(QtGui.QColor('#ff0000'))

    def get_flow(self):
        """
        获取流程
        :return:
        """
        flow = []
        for i in range(self.flow_items_table_widget.rowCount()):
            flow_item = {
                'index': self.flow_items_table_widget.item(i, 0).text(),
                'flow_name': self.flow_items_table_widget.item(i, 1).text(),
                'flow_start_delay': self.flow_items_table_widget.item(i, 2).text(),
                'flow_end_delay': self.flow_items_table_widget.item(i, 3).text(),
                'params': json.loads(self.flow_items_table_widget.item(i, 4).text()),
                'status': self.flow_items_table_widget.item(i, 5).text(),
                'color': self.flow_items_table_widget.item(i, 0).background().color().name()
            }
            flow.append(flow_item)
        return flow

    def execute_flow(self, flow):
        """
        执行流程
        :param flow:  流程
        :return:
        """
        # 把暂停按钮设置为可用
        self.stop_btn.setEnabled(True)
        # 把开始按钮设置为不可用
        self.start_btn.setEnabled(False)
        # 开启工作线程
        self.job_worker = FlowWorker(self, self.signals, flow)
        self.thread_pool.start(self.job_worker)

    def out_to_text_browser(self, text, level='info'):
        """
        输出信息到文本框
        :param text:  输出html的文本
        :return:  None
        """
        # 输出到日志文件
        output_log(text)
        # 格式化时间
        time_ = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
        if level == 'error':
            text = f'<font color="red">{time_} : {text}</font>'
        elif level == 'flow':
            text = f'<font color="#946726">{time_} : {text}</font>'
        text = f'{time_} <font color="green">{level.upper()}</font> : {text}'
        self.log_text_browser.append(text)
        QApplication.processEvents()

    def check_flow_compliance(self):
        """
        检查流程的合规性
        :return:
        """
        row_num = self.flow_items_table_widget.rowCount()
        if row_num == 0:
            QMessageBox.warning(self.parent, '提示', '请先添加流程')
            return False
        # 把第一列的颜色都取出来
        color_num = 0
        for i in range(row_num):
            color = self.flow_items_table_widget.item(i, 0).background().color().value()
            color_num ^= color
        if color_num != 0:
            QMessageBox.warning(self.parent, '提示', '首尾流程不匹配')
            return False
        return True

    def dead_cycle_check(self):
        """
        死循环检查
        :return:
        """
        # 获取流程
        flow = self.get_flow()
        # 检查是否有死循环
        if self.check_dead_cycle(flow):
            return True
        return False

    def check_dead_cycle(self, flow):
        """
        检查死循环
        :param flow:  流程
        :return: 如果有死循环返回True,否则返回False
        """
        do_loop = []
        # 遍历flow
        for i in range(len(flow)):
            if flow[i]['flow_name'] == '退出无限循环' and len(do_loop) == 0:
                QMessageBox.warning(self.parent, '提示', '`退出无限循环`指令不在`无限循环`指令中')
                return False
            if flow[i]['flow_name'] == '无限循环首':
                do_loop.append([1, 1])
            if flow[i]['flow_name'] == '退出无限循环':
                item = do_loop.pop()
                item.pop()
                do_loop.append(item)
            if flow[i]['flow_name'] == '无限循环尾':
                item = do_loop.pop()
                item.pop()
                if len(item) != 0:
                    return True
        return len(do_loop) != 0

    def closeEvent(self, event):
        """
        重写closeEvent方法，关闭窗体时触发
        :param event:
        :return:
        """
        sys.exit(0)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = QWidget()
    ui = DataCollectWidgetFunctionStarter(win)
    win.show()
    sys.exit(app.exec_())
